#!/usr/bin/env python
#
# wiki_report is used to make sure that the wiki is consistent.
#
# Later, we will do some graph analysis to insure that all pages
# are reachable from the main page, but for now all we want to
# know is that there aren't any missing pages.
#
# This is a submodule of warbler (warbler.org)
#
# Copyright (c) 2008 by Patrick Maupin <pmaupin@gmail.com>

import os
import glob
import re

def splitter():
    LeftBracket = '\\['
    Space = ' '
    Tab = '\t'
    NL = '\n'

    def join(*what):
        return ''.join(what)

    def precededby(what):
        return join('(?<=', what, ')')

    def followedby(what):
        return join('(?=', what, ')')

    def or_(*what):
        return '|'.join(what)

    def set_(*what):
        return '[%s]' % ''.join(what)

    def group(*what):
        return '(%s)' % or_(*what)

    def oneormore(what):
        return what + '+'

    def zeroormore(what):
        return what + '*'

    code = '{{{.*?}}}'

    uppercase = 'A-Z'
    lowercase = 'a-z'
    digits = '0-9'

    delimiter = set_('^_', digits, lowercase, uppercase)
    camelcase = join(
                    precededby(delimiter),
                    set_(uppercase), oneormore(set_(lowercase)),
                    set_(uppercase), oneormore(set_(lowercase)),
                    zeroormore(set_(join(uppercase, lowercase))),
                    followedby(delimiter),
                    )
    ref  = group(camelcase)

    regexp = or_(code, ref)
    return re.compile(regexp, re.MULTILINE).findall

def stripnavigation(fname):
    f = open(fname, 'rb')
    data = f.read()
    f.close()
    data = data.splitlines()
    index = 0
    while index < len(data):
        test = data[index].strip()
        if test and not test.startswith('#'):
            break
        index += 1
    for index in (index,-1):
        line = data[index].strip()
        if line.startswith('||') and line.endswith('||') and '>>' in line:
            if index == -1:
                data.pop()
            else:
                assert not data[index+1], (fname, index, data[index+1])
                del data[index:index+2]
    data.append('')
    return '\n'.join(data)

def get_hierarchy(tocfn):
    class AnnotatedString(str):
        def __init__(self, *args):
            self.children = []
    f = open(tocfn, 'rb')
    data = f.read().splitlines()
    f.close()
    data = [x for x in data if '* [' in x and '||' not in x]
    toppage = AnnotatedString('WarblerManual')
    toppage.indent = -1
    toppage.parent = toppage
    pagenames = [toppage]
    stack = [toppage]
    for line in data:
        pagename = AnnotatedString(line.split('[',1)[1].split()[0])
        pagename.indent = indent = len(line) - len(line.lstrip())
        pagenames.append(pagename)
        while indent <= stack[-1].indent:
            stack.pop()
        pagename.parent = stack[-1]
        pagename.parent.children.append(pagename)
        stack.append(pagename)

    for page1, page2 in zip(pagenames, pagenames[1:]):
        page1.next = page2
        page2.prev = page1

    pagenames[0].prev = pagenames[-1]
    pagenames[-1].next = pagenames[0]

    chapters = [x for x in pagenames if x.parent == toppage]
    for page1, page2 in zip(chapters, chapters[1:]):
        page1.nextchapter = page2
        page2.prevchapter = page1
    chapters[0].prevchapter = chapters[-1]
    chapters[-1].nextchapter = chapters[0]
    chapters = set(chapters)
    for page in pagenames:
        parent = page.parent
        if page not in chapters:
            page.prevchapter = parent
            page.nextchapter = parent.nextchapter
        page.indent = 1
        while parent != toppage:
            page.indent += 1
            parent = parent.parent

    return pagenames

def check_hierarchy(toppage, referenced):
    result = []
    children = set(toppage.children)
    missing = children - referenced.get(toppage, set())
    if missing:
            result.append("Page %s missing reference(s): %s" % (toppage, ', '.join(sorted(missing))))
    if toppage in children:
        children.remove(toppage)
    for pagename in children:
        result.extend(check_hierarchy(pagename, referenced))
    return result

def get_data():
    result = {}
    finder = splitter()

    wikipath = os.path.join(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')), '*.wiki')

    files = glob.glob(wikipath)
    files = dict(((os.path.splitext(os.path.basename(x))[0], x) for x in files))
    for fname, fullname in files.iteritems():
        data = finder(stripnavigation(fullname))
        data = [''.join(x) for x in data]
        result[fname] = set(x for x in data if x)

    return files, result

class WikiInfo(object):
    fnames, referenced = get_data()
    pageorder = get_hierarchy(fnames['WarblerManual'])

    available = set(fnames)

    all_refs = set()
    for x in referenced.values():
        all_refs |= x

    missing = all_refs - available
    unreferenced = available - all_refs

    singular = set([x for x in missing if (x+'s') in available])
    missing -= singular
    del x

    not_in_book = available - set(pageorder)

    def __repr__(self):
        result = []
        result.append('All pages: %s' % ', '.join(sorted(self.fnames)))
        result.append("Singular pages: %s" % ', '.join(sorted(self.singular)))
        result.append("Missing pages: %s" % ', '.join(sorted(self.missing)))
        result.append("Unreferenced pages: %s" % ', '.join(sorted(self.unreferenced)))
        result.append("Pages not in table of contents: %s" % ', '.join(sorted(self.not_in_book)))
        result.append('\n'.join(check_hierarchy(self.pageorder[0], self.referenced)))
        return '\n\n'.join(result)

if __name__ == '__main__':
    print
    print WikiInfo()
    print

